‹header›
‹date/time›
Click to edit Master text styles
Second level
Third level
Fourth level
Fifth level
‹footer›
‹#›
But what if the problem specification is static, the code is single use, and all of the data types are initially known? Then, depending on the problem size, probably some other methodology is more appropriate. For instance, a monolithic program which prints “hello world” will take a fraction of the machine instructions necessary to do the same task as the equivalent object-oriented program.
Consider the multiple lists example. The problem here with modular programming is, that you must explicitly create and destroy your list handles. Then you use the procedures of the module to modify each of your handles. In contrast to that, in object-oriented programming we would have as many list objects as needed. Instead of calling a procedure which we must provide with the correct list handle, we would directly send a message to the list object in question. Roughly speaking, each object implements its own module allowing for example many lists to coexist. Each object is responsible to initialize and destroy itself correctly. Consequently, there is no longer the need to explicitly call a creation or termination procedure.
The first thing with which one is confronted when writing programs is the problem. Typically you are confronted with ``real-life'' problems and you want to make life easier by providing a program for the problem. However, real-life problems are nebulous and the first thing you have to do is to try to understand the problem to separate necessary from unnecessary details: You try to obtain your own abstract view, or model, of the problem. The first thing with which one is confronted when writing programs is the problem. Typically you are confronted with ``real-life'' problems and you want to make life easier by providing a program for the problem. However, real-life problems are nebulous and the first thing you have to do is to try to understand the problem to separate necessary from unnecessary details: You try to obtain your own abstract view, or model, of the problem. This process of modeling is called abstraction.
Certainly not all of these properties are necessary to solve the administration problem. Only some of them are problem specific. Consequently you create a model of an employee for the problem. This model only implies properties which are needed to fulfill the requirements of the administration, for instance name, date of birth and social number. These properties are called the data of the (employee) model. Now you have described real persons with help of an abstract employee. Of course, the pure description is not enough. There must be some operations defined with which the administration is able to handle the abstract employees. For example, there must be an operation which allows you to create a new employee once a new person enters the institution. Consequently, you have to identify the operations which should be able to be performed on an abstract employee. You also decide to allow access to the employees' data only with associated operations. This allows you to ensure that data elements are always in a proper state. For example you are able to check if a provided date is valid. To sum up, abstraction is the structuring of a nebulous problem into well-defined entities by defining their data and operations. Consequently, these entities combine data and operations. They are not decoupled from each other.
An ADT which consists of an abstract data structure and operations. Only the operations are viewable from the outside and define the interface.
Point 3 of the ADT definition says that for each access to the data structure there must be an operation defined. The above access examples seem to contradict this requirement. Is this really true? Let's look again at the two possibilities for representing imaginary numbers. Let's stick to the real part. In the first version, x equals c[0]. In the second version, x equals c.r. In both cases x equals ``something''. It is this ``something'' which differs from the actual data structure used. But in both cases the performed operation ``equal'' has the same meaning to declare x to be equal to the real part of the complex number c: both cases archieve the same semantics. If you think of more complex operations the impact of decoupling data structures from operations becomes even more clear. For example the addition of two complex numbers requires you to perform an addition for each part. Consequently, you must access the value of each part which is different for each version. By providing an operation ``add'' you can encapsulate these details from its actual use. In an application context you simply ``add two complex numbers'' regardless of how this functionality is actually archieved. Once you have created an ADT for complex numbers, say Complex, you can use it in the same way like well-known data types such as integers.
Once you have created an ADT for complex numbers, say Complex, you can use it in the same way like well-known data types such as integers. Let's summarize this: The separation of data structures and operations and the constraint to only access the data structure via a well-defined interface allows you to choose data structures appropriate for the application environment.
The description above is a specification for the ADT Integer. Please notice, that we use words for names of operations such as ``add''. We could use the more intuitive ``+'' sign instead, but this may lead to some confusion: You must distinguish the operation ``+'' from the mathematical use of ``+'' in the postcondition. The name of the operation is just syntax whereas the semantics is described by the associated pre- and postconditions. However, it is always a good idea to combine both to make reading of ADT specifications easier. Real programming languages are free to choose an arbitrary implementation for an ADT. For example, they might implement the operation add with the infix operator ``+'' leading to a more intuitive look for addition of integers.
ADTs define functionality by putting main emphasis on the involved data, their structure, operations as well as axioms and preconditions. Consequently, object-oriented programming is ``programming with ADTs'': combining functionality of different ADTs to solve a problem. Therefore instances (objects) of ADTs (classes) are dynamically created, destroyed and used.
The last section introduces abstract data types (ADTs) as an abstract view to define properties of a set of entities. Object-oriented programming languages must allow to implement these types. Consequently, once an ADT is implemented we have a particular representation of it available. Consider again the ADT Integer. Programming languages such as Pascal, C, Modula-2 and others already offer an implementation for it. Sometimes it is called int or integer. Once you've created a variable of this type you can use its provided operations. For example, you can add two integers:
We now have a representation at two levels. The first level is the ADT level where we express everything that is done to an instance of this ADT by the invocation of defined operations. At this level, pre- and postconditions are used to describe what actually happens. In the following example, these conditions are enclosed in curly brackets.
{ Precondition: i = n where n is any Integer }
i.set(1)
{ Postcondition: i = 1 }
We now have a representation at two levels. The first level is the ADT level where we express everything that is done to an instance of this ADT by the invocation of defined operations. At this level, pre- and postconditions are used to describe what actually happens. In the following example, these conditions are enclosed in curly brackets. The ADT operation set is implemented.
The postcondition ensures that i and j do not change their values. Please recall the specification of add. It says that a new Integer is created the value of which is the sum. Consequently, we must provide a mechanism to access this new instance.
The postcondition ensures that i and j do not change their values. Please recall the specification of add. It says that a new Integer is created the value of which is the sum. Consequently, we must provide a mechanism to access this new instance.
A class is an actual representation of an ADT. It therefore provides implementation details for the data structure used and operations.
In the example above as well as in examples which follow we use a notation which is not programming language specific. In this notation class {...} denotes the definition of a class. Enclosed in the curly brackets are two sections attributes: and methods: which define the implementation of the data structure and operations of the corresponding ADT. Again we distinguish the two levels with different terms: At the implementation level we speak of “attributes'' which are elements of the data structure at the ADT level. The same applies to “methods'' which are the implementation of the ADT operations.
We only define two methods setValue() and addValue() representing the two operations set and add.
Recall the employee example. We have talked of instances of abstract employees. These instances are actual ``examples'' of an abstract employee, hence, they contain actual values to represent a particular employee. We call these instances objects. Objects are uniquely identifiable by a name. Therefore you could have two distinguishable objects with the same set of values. This is similar to ``traditional'' programming languages where you could have, say two integers i and j both of which equal to ``2''. Please notice the use of ``i'' and ``j'' in the last sentence to name the two integers. We refer to the set of values at a particular time as the state of the object.
The state of the object changes according to the methods which are applied to it. We refer to these possible sequence of state changes as the behaviour of the object. We now have two main concepts of object-orientation introduced, class and object. Object-oriented programming is therefore the implementation of abstract data types or, in more simple words, the writing of classes. At runtime instances of these classes, the objects, achieve the goal of the program by changing their states. Consequently, you can think of your running program as a collection of objects. The question arises of how these objects interact? We therefore introduce the concept of a message in the next section.
A running program is a pool of objects where objects are created, destroyed and interacting. This interacting is based on messages which are sent from one object to another asking the recipient to apply a method on itself. To give you an understanding of this communication, let's come back to the class Integer presented earlier. In our pseudo programming language we could create new objects and invoke methods on them. For example, we could use
Integer i; /* Define a new integer object */
i.setValue(1); /* Set its value to 1 */
to express the fact, that the integer object i should set its value to 1. This is the message ``Apply method setValue with argument 1 on yourself.'' sent to object i. We notate the sending of a message with ``.''. This notation is also used in C++; other object-oriented languages might use other notations, for example “- ''. Sending a message asking an object to apply a method is similar to a procedure call in “traditional'' programming languages. However, in object-orientation there is a view of autonomous objects which communicate with each other by exchanging messages. Objects react when they receive messages by applying methods on themselves. They also may deny the execution of a method, for example if the calling object is not allowed to execute the requested method. In our example, the message and the method which should be applied once the message is received have the same name: We send “setValue with argument 1'' to object i which applies “setValue(1)''. Consequently, invocation of a method is just a reaction caused by receipt of a message. This is only possible, if the method is actually known to the object.
Comparing both class definitions we can observe the following:
•Both classes have two data elements x and y. In the class Point these elements describe the position of the point, in the case of class Circle they describe the circle's center. Thus, x and y have the same meaning in both classes: They describe the position of their associated object by defining a point. •Both classes offer the same set of methods to get and set the value of the two data elements x and y. •Class Circle ``adds'' a new data element radius and corresponding access methods.
Since the class Circle is a kind of class Point, an instance of Circle, say acircle, is a point. Consequently, each circle behaves like a point. For example, you can move points in x direction by altering the value of x. Similarly, you move circles in this direction by altering their x value.
You sometimes need to be able to build objects by combining them out of others. You already know this from procedural programming, where you have the
structure or record construct to put data of various types together.
Let's come back to our drawing program. You already have created several classes for the available figures. Now you decide that you want to have a special figure which represents your own logo which consists of a circle and a triangle. (Let's assume, that you already have defined a class Triangle.) Thus, your logo consists of
two parts or the circle and triangle are part-of your logo.
You sometimes need to be able to build objects by combining them out of others. You already know this from procedural programming, where you have the
structure or record construct to put data of various types together.
Let's come back to our drawing program. You already have created several classes for the available figures. Now you decide that you want to have a special figure which represents your own logo which consists of a circle and a triangle. (Let's assume, that you already have defined a class Triangle.) Thus, your logo consists of
two parts or the circle and triangle are part-of your logo.
In the literature you may also find other terms for “superclass'' and “subclass''. Superclasses are also called parent classes. Subclasses may also be called child
classes or just derived classes.
Of course, you can again inherit from a subclass, making this class the superclass of the new subclass. This leads to a hierarchy of superclass/subclass relationships.
If you draw this hierarchy you get an inheritance graph.
In the literature you also find illustrations where the arrowed lines are used just the other way around. The direction in which the arrowed line is used, depends on
how the corresponding author has decided to understand it.
In this tutorial the arrowed line is always directed towards the superclass. In the further discussion an unmarked arrowed line indicates “inherit-from''.
This declares a class Point and defines an object apoint. You can think of a class definition as a structure definition with functions (or ``methods''). Additionally, you can specify the access rights in more detail. For example, _x and _y are private, because elements of classes are private as default. Consequently, we explicitly must ``switch'' the access rights to declare the following to be public. We do that by using the keyword public followed by a colon: Every element following this
keyword are now accessible from outside of the class.
This declares a class Point and defines an object apoint. You can think of a class definition as a structure definition with functions (or ``methods''). Additionally, you can specify the access rights in more detail. For example, _x and _y are private, because elements of classes are private as default. Consequently, we explicitly must ``switch'' the access rights to declare the following to be public. We do that by using the keyword public followed by a colon: Every element following this
keyword are now accessible from outside of the class.
Recall that a structure struct is a combination of various data elements which are accessible from the outside. We are now able to express a structure with help of
a class, where all elements are declared to be public.
This is exactly what C++ does with struct. Structures are handled like classes. Whereas elements of classes (defined with class) are private by default, elements of structures (defined with struct) are public. However, we can also use private: to switch to a private section in structures.
Let's come back to our class Point. Its interface starts with the public section where we define four methods. Two for each coordinate to set and get its value. The set methods are only declared. Their actual functionality is still to be defined. The get methods have a function body: They are defined within the class or, in other
words, they are inlined methods.
This type of method definition is useful for small and simple bodies. It also improve performance, because bodies of inlined methods are “copied'' into the code
wherever a call to such a method takes place.
On the contrary, calls to the set methods would result in a ``real'' function call. We define these methods outside of the class declaration. This makes it necessary, to indicate to which class a method definition belongs to. For example, another class might just define a method setX() which is quite different from that of Point. We must be able to define the scope of the definition; we therefore use the scope operator “::'‘. Here we define method setX() (setY()) within the scope of class Point. The object apoint can use these methods to set and get information about itself:
Here we explicitly use the pointer this to explicitly dereference the invoking object. Fortunately, the compiler automatically ``inserts'' these dereferences for class members, hence, we really can use the first definitions of setX() and setY(). However, it sometimes make sense to know that there is a pointer this available which
indicates the invoking object.
Currently, we need to call the set methods to initialize a point object. However, we would like to initialize the point when we define it. We therefore use special
methods called constructors.
C++ is a bunch of small additions to C, and one major addition. This one addition is the object-oriented approach. As its name suggests, this deals with objects. Of course, these are not real-life objects. Instead, these objects are the essential definitions of real world objects. Structures are one step away from these objects, they do not possess one element of them: functions. The definitions of these objects are called classes. The easiest way to think about a class is to imagine a structure that has functions.
What is this mysterious structure (not the programming type)? Well, it is not only a collection of variables under one heading, but it is a collection of functions under that same heading. If the structure is a house, then the functions will be the doors and the variables will be the items inside the house. They usually will be the only way to modify the variables in this structure, and they are usually the only to access the variables in this structure.
The idea to make programs more modular. A section of code will have its own functions and variables that control what it can do, and it does not require anything outside of itself to function. While the class may require initialization with data, it does not require outside variables or functions to manipulate the data. That allows programs to reuse the same code more easily.
From now on, we shall call these structures with functions classes (I guess Marx would not like C++). The syntax for these classes is simple. First, you put the keyword 'class' then the name of the class. Our example will use the name computer. Then you put an open bracket. Before putting down the different variables, it is necessary to put the degree of restriction on the variable. There are three levels of restriction. The first is public, the second protected, and the third private. For now, all you need to know is that the public restriction allows any part of the program, including that which is not part of the class, access the variables specified as public. The protected restriction prevents functions outside the class to access the variable. The syntax for that is merely the restriction keyword (public, private, protected) and then a colon. Finally, you put the different variables and functions (You usually will only put the function prototype[s]) you want to be part of the class. Then you put a closing bracket and semicolon. Keep in mind that you still must end the function prototype(s) with a semi-colon.
Constructors have the same name of the class (thus they are identified to be constructors). They have no return value. As other methods, they can take arguments. Constructors have the same name of the class (thus they are identified to be constructors). They have no return value. As other methods, they can take arguments. For example, we may want to initialize a point to other coordinates than (0, 0). We therefore define a second constructor taking two integer arguments within the class.
If we want to create a point from another point, hence, copying the properties of one object to a newly created one, we sometimes have to take care of the copy process. For example, consider the class List which allocates dynamically memory for its elements. If we want to create a second list which is a copy of the first, we
must allocate memory and copy the individual elements.
This type of constructor is so important that it has its own name: copy constructor. It is highly recommended that you provide for each of your classes such a constructor, even if it is as simple as in our example. The copy constructor is called in the following cases:
Point apoint; // Point::Point()
Point bpoint(apoint); // Point::Point(const Point &)
Point cpoint = apoint; // Point::Point(const Point &)
With help of constructors we have fulfilled one of our requirements of implementation of abstract data types: Initialization at definition time. We still need a mechanism which automatically “destroys'' an object when it gets invalid (for example, because of leaving its scope). Therefore, classes can define destructors.
Consider a class List. Elements of the list are dynamically appended and removed. The constructor helps us in creating an initial empty list. However, when we leave the scope of the definition of a list object, we must ensure that the allocated memory is released. We therefore define a special method called destructor which
is called once for each object at its destruction time:
void zoo() {
List alist; // List::List() initializes to
// empty list.
... // add/remove elements
} // Destructor call!
Destruction of objects take place when the object leaves its scope of definition or is explicitly destroyed. The latter happens, when we dynamically allocate an object
and release it when it is no longer needed.
Destructors are declared similar to constructors. Thus, they also use the name prefixed by a tilde (~ ) of the defining class: Destructors take no arguments. It is even invalid to define one, because destructors are implicitly called at destruction time: You have no chance to specify actual
arguments.
In our pseudo language, we formulate inheritance with “inherits from''. In C++ these words are replaced by a colon. As an example let's design a class for 3D
points. Of course we want to reuse our already existing class Point.
The leftmost column lists possible access rights for elements of classes. It also includes a third type protected. This type is used for elements which should be directly usable in subclasses but which should not be accessible from the outside. Thus, one could say elements of this type are between private and public elements in that they can be used within the class hierarchy rooted by the corresponding class. The second and third column show the resulting access right of the elements of a superclass when the subclass is privately and publically derived, respectively.
This concept makes easy to prevent variables that are contained (or owned) by the class being overwritten accidentally. It also allows a totally different way of thinking about programming.